// 16.5 模板特例化
/**
 * 编写单一模板，使之对任何可能的模板实参都是最适合的，都能实例化，这并不总是能办到。
 * 在某些情况下，通用模板的定义对特定类型是不适合的：通用定义可能编译失败或做得不正确。其他时候，我们也可以利用某些特定知识来编写更高效的代码，而不是从通用模板实例化。
 * 当我们不能（或不希望）使用模板版本时，可以定义类或函数模板的一个特例化版本。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy;
#include <map>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <memory>
#include <new>
using namespace std;
#include <functional>
#include "../VisualStudio2012/15/Quote.h"
#include "../VisualStudio2012/12/TextQuery.h"

int main()
{
    // 16.5 模板特例化
    /*
我们的compare函数是一个很好的例子，它展示了函数模板的通用定义不适合一个特定类型（即字符指针）的情况。
// 第一个版本；可以比较任意类型
template<typename T> int compare(const T&, const T&);
// 第二个版本处理字符串字面常量
template<size_t N, size_t M>
int compare(const char (&a)[N], const char (&b)[M]);
但是，只有当我们传递给compare一个字符串字面常量或者一个数组时，编译器才会调用接受两个非类型模板参数的版本。如果我们传递给它字符指针，就会调用第一个版本：
const char *p1 = "hi", *p2 = "bye";
compare(p1, p2);      // 调用第一个模板
compare("hi", "bye"); // 调用有两个非类型参数的模板
我们无法将一个指针转换为一个数组的引用，因此当参数是p1和p2时，第二个版本的compare是不可行的。
为了处理字符指针（而不是数组），可以为第一个版本的compare定义一个模板特例化（template specialization）版本。
  一个特例化版本就是模板的一个独立的定义，在其中一个或多个模板参数被指定为特定的类型。
    */

    // 定义函数模板特例化
    /*
当我们特例化一个函数模板时，必须为原模板中的每个模板参数都提供实参。为了指出我们正在实例化一个模板，应使用关键字template后跟一个空尖括号对（<>）。
  空尖括号指出我们将为原模板的所有模板参数提供实参：
// compare的特殊版本，处理字符数组的指针
template<>
int compare(const char* const &p1, const char* const &p2)
{
  return strcmp(p1, p2);
}
理解此特例化版本的困难之处是函数参数类型。当我们定义一个特例化版本时，函数参数类型必须与一个先前声明的模板中对应的类型匹配。
  本例中我们特例化：[插图]
template<typename T> int compare(cosnt T&, const T&);
其中函数参数为一个const类型的引用。类似类型别名，模板参数类型、指针及const之间的相互作用会令人惊讶（参见2.5.1节，第60页）。
我们希望定义此函数的一个特例化版本，其中T为const char＊。我们的函数要求一个指向此类型const版本的引用。
  一个指针类型的const版本是一个常量指针而不是指向const类型的指针（参见2.4.2节，第56页）。我们需要在特例化版本中使用的类型是const char ＊ const &，即一个指向const char的const指针的引用。
    */
    
    // 函数重载与模板特例化
    // 当定义函数模板的特例化版本时，我们本质上接管了编译器的工作。即，我们为原模板的一个特殊实例提供了定义。重要的是要弄清：一个特例化版本本质上是一个实例，而非函数名的一个重载版本。
    // 特例化的本质是实例化一个模板，而非重载它。因此，特例化不影响函数匹配。

    // 关键概念：普通作用域规则应用于特例化
    // 为了特例化一个模板，原模板的声明必须在作用域中。而且，在任何使用模板实例的代码之前，特例化版本的声明也必须在作用域中。
    // 如果丢失了一个特例化版本的声明，编译器通常可以用原模板生成代码。由于在丢失特例化版本时编译器通常会实例化原模板，很容易产生模板及其特例化版本声明顺序导致的错误，而这种错误又很难查找。
    // 模板及其特例化版本应该声明在同一个头文件中。所有同名模板的声明应该放在前面，然后是这些模板的特例化版本。

    // 类模板特例化
    /*
除了特例化函数模板，我们还可以特例化类模板。
作为一个例子，我们将为标准库hash模板定义一个特例化版本，可以用它来将Sales_data对象保存在无序容器中。默认情况下，无序容器使用hash<key_type>（参见11.4节，第394页）来组织其元素。
  为了让我们自己的数据类型也能使用这种默认组织方式，必须定义hash模板的一个特例化版本。一个特例化hash类必须定义：
  · 一个重载的调用运算符（参见14.8节，第506页），它接受一个容器关键字类型的对象，返回一个size_t。
  · 两个类型成员，result_type和argument_type，分别调用运算符的返回类型和参数类型。
  · 默认构造函数和拷贝赋值运算符（可以隐式定义，参见13.1.2节，第443页）。
我们可以向命名空间添加成员。为了达到这一目的，首先必须打开命名空间：
// 打开std命名空间，以便特例化std::hash
namespace std {
template<>
struct hash<Sales_data> {
  // 用来散列一个无序容器的类型必须定义下列类型
  typedef size_t result_type;
  typedef Sales_data argument_type; // 默认情况下，此类型需要==
  size_t operator()(const Sales_data &data) const;
  // 我们的类使用合成的拷贝控制成员和默认构造函数
};
size_t
hash<Sales_data>::operator()(const Sales_data &s) const
{
  return std::hash<std::string>()(s.bookNo()) ^
         std::hash<unsigned>()(s.units_sold) ^
         std::hash<double>()(s.revenue());
}
} // 关闭std命名空间，注意又花括号没有分号
假定我们的特例化版本在作用域中，当将Sales_data作为容器的关键字类型时，编译器就会自动使用此特例化版本：
// 使用hash<Sales_data>和14.3.1节中Sales_data的operator==
unordered_multiset<Sales_data> SDset;
由于hash<Sales_data>使用Sales_data的私有成员，我们必须将它声明为Sales_data的友元：
template<class T> class std::hash; // 友元声明所需要的
class Sales_data {
friend class std::hash<Sales_data>;
  // 其他成员定义，如前
};
    */
    // 为了让Sales_data的用户能使用hash的特例化版本，我们应该在Sales_data的头文件中定义该特例化版本。
    
    // 类模板部分特例化
    /*
与函数模板不同，类模板的特例化不必为所有模板参数提供实参。我们可以只指定一部分而非所有模板参数，或是参数的一部分而非全部特性。
  一个类模板的部分特例化（partial specialization）本身是一个模板，使用它时用户还必须为那些在特例化版本中未指定的模板参数提供实参。
我们只能部分特例化类模板，而不能部分特例化函数模板。
在16.2.3节（第605页）中我们介绍了标准库remove_reference类型。该模板是通过一系列的特例化版本来完成其功能的：
// 原始的、最通用的版本
template<class T> struct remove_reference {
  typedef T type;
}
// 部分特例化，将用于左值引用和右值引用
template<class T> struct remove_reference<T&> { // 左值引用
  typedef T type;
}
template<class T> struct remove_reference<T&&> { // 右值引用
  typedef T type;
}
由于一个部分特例化版本本质是一个模板，与往常一样，我们首先定义模板参数。类似任何其他特例化版本，部分特例化版本的名字与原模板的名字相同。
  对每个未完全确定类型的模板参数，在特例化版本的模板参数列表中都有一项与之对应。在类名之后，我们为要特例化的模板参数指定实参，这些实参列于模板名之后的尖括号中。
  这些实参与原始模板中的参数按位置对应。
int i;
// decltype(42)为int，使用原始模板
remove_reference<decltype(42)>::type a;
// decltype(i)为int&，使用第一个（T&）部分特例化版本
remove_reference<decltype(i)>::type b;
// decltype(std::move(i))为int&&，使用第二个（T&&）部分特例化版本
remove_reference<decltype(std::move(i))>::type c;
    */

    // 特例化成员而不是类
    // 我们可以只特例化特定成员函数而不是特例化整个模板。例如，如果Foo是一个模板类，包含一个成员Bar，我们可以只特例化该成员：
    /*
template<typename T> struct Foo {
  Foo(const T& t = T()) : mem(t) {}
  void Bar() {}
  T mem;
};
template<>           // 我们正在特例化一个模板
void Foo<int>::Bar() // 我们正在特例化Foo<int>的成员Bar
{
  // 进行应用于int的特例化处理
}
本例中我们只特例化Foo<int>类的一个成员，其他成员将由Foo模板提供：
Foo<string> fs; // 实例化Foo<string>::Foo()
fs.Bar();       // 实例化Foo<string>::Bar()
Foo<int> fi;    // 实例化Foo<int>::Foo()
fi.Bar();       // 使用我们特例化版本的Foo<int>::Bar()
    */

    // 小结
    /*
模板是C++语言与众不同的特性，也是标准库的基础。一个模板就是一个编译器用来生成特定类类型或函数的蓝图。生成特定类或函数的过程称为实例化。
  我们只编写一次模板，就可以将其用于多种类型和值，编译器会为每种类型和值进行模板实例化。
我们既可以定义函数模板，也可以定义类模板。标准库算法都是函数模板，标准库容器都是类模板。
显式模板实参允许我们固定一个或多个模板参数的类型或值。对于指定了显式模板实参的模板参数，可以应用正常的类型转换。
一个模板特例化就是一个用户提供的模板实例，它将一个或多个模板参数绑定到特定类型或值上。当我们不能（或不希望）将模板定义用于某些特定类型时，特例化非常有用。
最新C++标准的一个主要部分是可变参数模板。一个可变参数模板可以接受数目和类型可变的参数。可变参数模板允许我们编写像容器的emplace成员和标准库make_shared函数这样的函数，实现将实参传递给对象的构造函数。
    */

    return 0;
}